from __future__ import annotations
from enum import IntEnum
import ctypes
import os
import numpy as np
import weakref
import typing
import sys
import math

__all__ = ["export_fn", "call_fn"]

_TONGLUN_CORELIB_PTR = os.environ.get("TONGLUN_CORELIB_PTR")
if _TONGLUN_CORELIB_PTR is None:    
    _TONGLUN_CORELIB_PATH = os.environ.get("TONGLUN_CORELIB_PATH")
    if _TONGLUN_CORELIB_PATH is None:
        raise RuntimeError("TONGLUN_CORELIB_PATH is not set")

    # Load the dynamic library
    _lib = ctypes.CDLL(_TONGLUN_CORELIB_PATH)
    os.environ["TONGLUN_CORELIB_PTR"] = str(_lib._handle)
else:
    _lib = ctypes.CDLL(name="libtonglun_core", handle=int(_TONGLUN_CORELIB_PTR))

# Type definitions
TLError = ctypes.c_uint64


_PyMemoryView_FromMemory = ctypes.pythonapi.PyMemoryView_FromMemory
_PyMemoryView_FromMemory.restype = ctypes.py_object
_PyMemoryView_FromMemory.argtypes = (ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int)


class TLValueTag(IntEnum):
    """Value type tags matching the C enum"""

    NONE = 1
    BOOL = 2
    INT64 = 3
    FLOAT64 = 4
    COMPLEX32 = 5
    COMPLEX64 = 6
    STRING = 7
    ARRAY = 8
    HLIST = 9


class TLArrayDType(IntEnum):
    """Array data types matching the C enum"""

    BOOL = 0
    INT8 = 1
    INT16 = 2
    INT32 = 3
    INT64 = 4
    UINT8 = 5
    UINT16 = 6
    UINT32 = 7
    UINT64 = 8
    FLOAT32 = 9
    FLOAT64 = 10
    COMPLEX32 = 11
    COMPLEX64 = 12


class TLValue(ctypes.Structure):
    """Value reference structure matching the C struct"""

    _fields_ = [("tag", ctypes.c_uint8), ("data", ctypes.c_uint64)]


# Function pointer types
StringDropCallback = ctypes.CFUNCTYPE(None, ctypes.c_void_p)
ArrayDropCallback = ctypes.CFUNCTYPE(None, ctypes.c_uint64)
TonglunFunction = ctypes.CFUNCTYPE(
    TLError,
    ctypes.c_uint64,          # data
    ctypes.POINTER(TLValue),  # out
    ctypes.POINTER(TLValue),  # args
    ctypes.c_uint64,
)  # nargs

# Runtime management functions
_lib.tonglun_create_runtime.argtypes = [
    ctypes.POINTER(ctypes.c_uint32),  # out_runtime_id
    ctypes.c_void_p,  # dlhandle
    ctypes.c_char_p,  # name
    ctypes.c_uint8,  # thread_safe
    ctypes.c_void_p,  # string_drop_callback
    ctypes.c_void_p,  # array_drop_callback
]
_lib.tonglun_create_runtime.restype = TLError

# Value management functions
_lib.tonglun_free_value.argtypes = [ctypes.POINTER(TLValue)]
_lib.tonglun_free_value.restype = TLError

# Value creation functions
_lib.tonglun_create_string_copied.argtypes = [ctypes.POINTER(TLValue), ctypes.c_char_p]
_lib.tonglun_create_string_copied.restype = TLError

_lib.tonglun_create_string_borrowed.argtypes = [
    ctypes.POINTER(TLValue),
    ctypes.c_uint32,
    ctypes.c_char_p,
    ctypes.c_void_p,
]
_lib.tonglun_create_string_borrowed.restype = TLError

_lib.tonglun_create_none.argtypes = [ctypes.POINTER(TLValue)]
_lib.tonglun_create_none.restype = TLError

_lib.tonglun_create_bool.argtypes = [ctypes.POINTER(TLValue), ctypes.c_uint8]
_lib.tonglun_create_bool.restype = TLError

_lib.tonglun_create_int64.argtypes = [ctypes.POINTER(TLValue), ctypes.c_int64]
_lib.tonglun_create_int64.restype = TLError

_lib.tonglun_create_float64.argtypes = [ctypes.POINTER(TLValue), ctypes.c_double]
_lib.tonglun_create_float64.restype = TLError

_lib.tonglun_create_complex32.argtypes = [
    ctypes.POINTER(TLValue),
    ctypes.c_float,
    ctypes.c_float,
]
_lib.tonglun_create_complex32.restype = TLError

_lib.tonglun_create_complex64.argtypes = [
    ctypes.POINTER(TLValue),
    ctypes.c_double,
    ctypes.c_double,
]
_lib.tonglun_create_complex64.restype = TLError

_lib.tonglun_create_array.argtypes = [
    ctypes.POINTER(TLValue),  # out
    ctypes.c_uint32,  # runtime_id
    ctypes.c_char_p,  # typecode
    ctypes.c_void_p,  # data
    ctypes.c_int64,  # ndims
    ctypes.POINTER(ctypes.c_size_t),  # dimensions
    ctypes.POINTER(ctypes.c_int64),  # strides
    ctypes.c_void_p,  # foreign_ob
    ctypes.c_uint8,  # readonly
    ctypes.c_uint8,  # fortran_order
]
_lib.tonglun_create_array.restype = TLError

_lib.tonglun_create_tuple.argtypes = [
    ctypes.POINTER(TLValue),
    ctypes.c_uint64,
    ctypes.POINTER(TLValue),
]
_lib.tonglun_create_tuple.restype = TLError

# Value access functions
_lib.tonglun_value_tag.argtypes = [ctypes.POINTER(TLValue)]
_lib.tonglun_value_tag.restype = ctypes.c_int

_lib.tonglun_get_bool.argtypes = [ctypes.POINTER(TLValue)]
_lib.tonglun_get_bool.restype = ctypes.c_uint8

_lib.tonglun_get_int64.argtypes = [ctypes.POINTER(TLValue)]
_lib.tonglun_get_int64.restype = ctypes.c_int64

_lib.tonglun_get_float64.argtypes = [ctypes.POINTER(TLValue)]
_lib.tonglun_get_float64.restype = ctypes.c_double

_lib.tonglun_load_complex32.argtypes = [
    ctypes.POINTER(ctypes.c_float),
    ctypes.POINTER(ctypes.c_float),
    ctypes.POINTER(TLValue),
]
_lib.tonglun_load_complex32.restype = None

_lib.tonglun_load_complex64.argtypes = [
    ctypes.POINTER(ctypes.c_double),
    ctypes.POINTER(ctypes.c_double),
    ctypes.POINTER(TLValue),
]
_lib.tonglun_load_complex64.restype = None

_lib.tonglun_load_string.argtypes = [
    ctypes.POINTER(ctypes.c_char_p),
    ctypes.POINTER(TLValue),
]
_lib.tonglun_load_string.restype = None

_lib.tonglun_get_tuple_length.argtypes = [ctypes.POINTER(TLValue)]
_lib.tonglun_get_tuple_length.restype = ctypes.c_uint64

_lib.tonglun_take_tuple.argtypes = [ctypes.POINTER(TLValue), ctypes.POINTER(TLValue)]
_lib.tonglun_take_tuple.restype = TLError

_lib.tonglun_load_array_info.argtypes = [
    ctypes.POINTER(ctypes.c_void_p),  # data
    ctypes.POINTER(ctypes.POINTER(ctypes.c_size_t)),  # dimensions
    ctypes.POINTER(ctypes.POINTER(ctypes.c_int64)),  # strides
    ctypes.POINTER(ctypes.c_int64),  # ndims
    ctypes.POINTER(ctypes.c_int),  # dtype (enum as int)
    ctypes.POINTER(ctypes.c_uint8),  # readonly
    ctypes.POINTER(ctypes.c_uint8),  # fortran_order
    ctypes.c_uint64,  # handle
]
_lib.tonglun_load_array_info.restype = None

# Function registration and calling
_lib.tonglun_register_function.argtypes = [
    ctypes.c_uint32,
    ctypes.c_char_p,
    ctypes.c_void_p,
    ctypes.c_uint64,
]
_lib.tonglun_register_function.restype = TLError

_lib.tonglun_call_function.argtypes = [
    ctypes.POINTER(TLValue),
    ctypes.c_char_p,
    ctypes.POINTER(TLValue),
    ctypes.c_uint64,
]
_lib.tonglun_call_function.restype = TLError

# Error handling functions
_lib.tonglun_try_load_error_message.argtypes = [
    ctypes.c_char_p,
    ctypes.c_uint64,
    TLError,
]
_lib.tonglun_try_load_error_message.restype = TLError

_lib.tonglun_error_buflen.argtypes = [TLError]
_lib.tonglun_error_buflen.restype = ctypes.c_uint64

_lib.tonglun_free_error.argtypes = [TLError]
_lib.tonglun_free_error.restype = TLError

_lib.tonglun_binding_error.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
_lib.tonglun_binding_error.restype = TLError

_lib.tonglun_run_deferred_free.argtypes = [ctypes.c_uint32]
_lib.tonglun_run_deferred_free.restype = TLError


_ID_ARRAY_MAPS = {}

class TonglunError(Exception):
    """Exception raised for Tonglun library errors"""

    def __init__(self, message: str = None):
        if message is None:
            message = self._get_error_message()
        super().__init__(message)


def _check_error(error_code: int) -> None:
    """Check error code and raise exception if non-zero"""
    if error_code != 0:
        # load the error message and free the error handle
        # guess a common size
        bufferlen = 1024
        buffer = ctypes.create_string_buffer(bufferlen + 1)
        alloc_err = _lib.tonglun_try_load_error_message(buffer, bufferlen, error_code)
        if alloc_err == 0:
            err_msg = buffer.value.decode("utf-8")
        else:
            # get the size from `alloc_err`
            real_buflen = _lib.tonglun_error_buflen(alloc_err)
            # free `alloc_err`
            _lib.tonglun_free_error(alloc_err)
            if real_buflen == 0:
                raise RuntimeError("Failed to load error message [alloc_err = 0]")
            buffer = ctypes.create_string_buffer(real_buflen + 1)

            alloc_err = _lib.tonglun_try_load_error_message(
                buffer, real_buflen, error_code
            )
            if alloc_err == 0:
                err_msg = buffer.value.decode("utf-8")
            else:
                raise RuntimeError("Failed to load error message [alloc_err != 0]")

        _lib.tonglun_free_error(error_code)
        raise TonglunError(err_msg)


def _dealloc_py_array(ob_id):
    """Deallocate a Python array"""
    if ob_id is not None:
        _ID_ARRAY_MAPS.pop(ob_id, None)


_REGISTERD_PYTHON_CALLBACKS: dict[int, typing.Callable] = {}

def universal_callback(data, out, args, nargs):
    try:
            arguments = []
            for i in range(nargs):
                value_ref = args[i]
                arguments.append(from_value_ref(value_ref))
            func = _REGISTERD_PYTHON_CALLBACKS[data]
            result = func(*arguments)
            out[0] = to_value_ref(result)
            return 0
    except Exception as e:
        # TODO: stacktrace
        error_message = str(e).encode("utf-8", errors="replace")
        language = b"python"
        error_code = _lib.tonglun_binding_error(language, error_message)
        return error_code

class _TonglunRuntimeType:
    """Python wrapper for Tonglun runtime"""

    name = b"python"

    def __init__(self):
        self._runtime_id = ctypes.c_uint32()
        # Keep references to function pointers to prevent garbage collection
        self._function_refs: dict[str, int] = {}
        self._universal_callback = TonglunFunction(universal_callback)

        # Get current Python interpreter handle for dlhandle
        dlhandle = ctypes.pythonapi._handle
        if not dlhandle:
            raise RuntimeError("Failed to get Python interpreter handle")

        # Keep reference to array drop callback to prevent garbage collection
        self._array_drop_callback = ArrayDropCallback(_dealloc_py_array)

        result = _lib.tonglun_create_runtime(
            ctypes.byref(self._runtime_id),
            dlhandle,
            self.name,
            0,  # Python is not thread-safe in multi-threaded environment
            ctypes.c_void_p(0),
            # numpy array deallocation callback
            ctypes.cast(self._array_drop_callback, ctypes.c_void_p),
        )
        _check_error(result)

    @property
    def runtime_id(self) -> int:
        return self._runtime_id.value

    def register_function(self, name: str | bytes, func) -> None:
        """Register a Python function to be callable from other languages"""
        if isinstance(name, str):
            name_bytes = name.encode("utf-8", errors="replace")
        else:
            name_bytes = name
            name = name.decode("utf-8", errors="replace")
        
        # Convert Python function to C function pointer
        # Keep a reference to prevent garbage collection
        func_id = id(func)
        if name in self._function_refs:
            previous_id = self._function_refs.pop(name)
            _REGISTERD_PYTHON_CALLBACKS.pop(previous_id, None)

        _REGISTERD_PYTHON_CALLBACKS[func_id] = func
        self._function_refs[name] = func_id
        
        result = _lib.tonglun_register_function(
            self._runtime_id.value, name_bytes, ctypes.cast(self._universal_callback, ctypes.c_void_p), func_id
        )
        _check_error(result)

    def run_deferred_free(self) -> None:
        """Run deferred cleanup for non-thread-safe runtimes"""
        result = _lib.tonglun_run_deferred_free(self._runtime_id.value)
        _check_error(result)


# High-level value creation functions
def create_none(_) -> TLValue:
    """Create a None value"""
    value = TLValue()
    result = _lib.tonglun_create_none(ctypes.byref(value))
    _check_error(result)
    return value


def create_bool(val: bool) -> TLValue:
    """Create a boolean value"""
    value = TLValue()
    result = _lib.tonglun_create_bool(ctypes.byref(value), 1 if val else 0)
    _check_error(result)
    return value


def create_int64(val: int) -> TLValue:
    """Create a 64-bit integer value"""
    value = TLValue()
    result = _lib.tonglun_create_int64(ctypes.byref(value), val)
    _check_error(result)
    return value


def create_float64(val: float) -> TLValue:
    """Create a 64-bit float value"""
    value = TLValue()
    result = _lib.tonglun_create_float64(ctypes.byref(value), val)
    _check_error(result)
    return value


def create_complex64(val: complex) -> TLValue:
    """Create a 64-bit complex value"""
    value = TLValue()
    result = _lib.tonglun_create_complex64(ctypes.byref(value), val.real, val.imag)
    _check_error(result)
    return value


def create_string(s: str) -> TLValue:
    """Create a string value (copied or borrowed)"""
    value = TLValue()
    s_bytes = s.encode("utf-8")

    # Python has no native UTF-8 encoding string type, so we always copy them
    result = _lib.tonglun_create_string_copied(ctypes.byref(value), s_bytes)

    _check_error(result)
    return value


def create_tuple(value: typing.Sequence) -> TLValue:
    value_refs = [to_value_ref(v) for v in value]
    out = TLValue()
    result = _lib.tonglun_create_tuple(
        ctypes.byref(out), len(value_refs), (TLValue * len(value_refs))(*value_refs)
    )
    _check_error(result)
    return out


def create_array(value: np.ndarray) -> TLValue:
    dtype = value.dtype.type
    typecode = _CREATE_ARRAY_DISPATCH.get(dtype, None)
    if typecode is None:
        raise ValueError(f"Unsupported numpy element type: {dtype}")

    out = TLValue()
    ndims = len(value.shape)
    strides = (ctypes.c_int64 * ndims)(*value.strides)
    dimensions = (ctypes.c_size_t * ndims)(*value.shape)
    runtime_id = get_runtime().runtime_id
    
    # Keep a reference to the numpy array to prevent garbage collection    
    foreign_ob = ctypes.c_void_p(id(value))
    _ID_ARRAY_MAPS[foreign_ob.value] = value

    
    result = _lib.tonglun_create_array(
        ctypes.byref(out),  # out
        runtime_id,  # runtime_id
        typecode,  # typecode
        value.ctypes.data,  # data
        ndims,  # ndims
        dimensions,  # dimensions
        strides,  # strides
        foreign_ob,  # foreign_ob
        not value.flags.writeable,  # readonly
        value.flags.fortran,  # fortran_order
    )
    _check_error(result)
    return out


_CREATE_VALUE_DISPATCH = {
    type(None): create_none,
    bool: create_bool,
    int: create_int64,
    float: create_float64,
    complex: create_complex64,
    str: create_string,
    np.ndarray: create_array,
    tuple: create_tuple,
    list: create_tuple,
}

_CREATE_ARRAY_DISPATCH = {
    np.bool_: b"b8",
    np.int8: b"i8",
    np.int16: b"i16",
    np.int32: b"i32",
    np.int64: b"i64",
    np.uint8: b"u8",
    np.uint16: b"u16",
    np.uint32: b"u32",
    np.uint64: b"u64",
    np.float32: b"f32",
    np.float64: b"f64",
    np.complex64: b"c32",
    np.complex128: b"c64",
}

_DTYPE_TO_NP_DTYPE = {
    TLArrayDType.BOOL: np.dtype(np.bool_),
    TLArrayDType.INT8: np.dtype(np.int8),
    TLArrayDType.INT16: np.dtype(np.int16),
    TLArrayDType.INT32: np.dtype(np.int32),
    TLArrayDType.INT64: np.dtype(np.int64),
    TLArrayDType.UINT8: np.dtype(np.uint8),
    TLArrayDType.UINT16: np.dtype(np.uint16),
    TLArrayDType.UINT32: np.dtype(np.uint32),
    TLArrayDType.UINT64: np.dtype(np.uint64),
    TLArrayDType.FLOAT32: np.dtype(np.float32),
    TLArrayDType.FLOAT64: np.dtype(np.float64),
    TLArrayDType.COMPLEX32: np.dtype(np.complex64),
    TLArrayDType.COMPLEX64: np.dtype(np.complex128),
}


_ENDIAN = '<' if sys.byteorder == 'little' else '>'
_TYPE_MAPPING = {
        TLArrayDType.BOOL: '|b1',  # boolean doesn't need endianness
        TLArrayDType.INT8: '|i1',   # single byte doesn't need endianness
        TLArrayDType.INT16: f'{_ENDIAN}i2',
        TLArrayDType.INT32: f'{_ENDIAN}i4', 
        TLArrayDType.INT64: f'{_ENDIAN}i8',
        TLArrayDType.UINT8: '|u1',
        TLArrayDType.UINT16: f'{_ENDIAN}u2',
        TLArrayDType.UINT32: f'{_ENDIAN}u4',
        TLArrayDType.UINT64: f'{_ENDIAN}u8',
        TLArrayDType.FLOAT32: f'{_ENDIAN}f4',
        TLArrayDType.FLOAT64: f'{_ENDIAN}f8',
        TLArrayDType.COMPLEX32: f'{_ENDIAN}c8',
        TLArrayDType.COMPLEX64: f'{_ENDIAN}c16',
    }

def get_value_tag(value: TLValue) -> TLValueTag:
    """Get the type tag of a value"""
    tag_int = _lib.tonglun_value_tag(ctypes.byref(value))
    return TLValueTag(tag_int)


def _get_bool(value: TLValue) -> bool:
    """Extract boolean from value"""
    return _lib.tonglun_get_bool(ctypes.byref(value)) != 0


def _get_int64(value: TLValue) -> int:
    """Extract 64-bit integer from value"""
    return _lib.tonglun_get_int64(ctypes.byref(value))


def _get_float64(value: TLValue) -> float:
    """Extract 64-bit float from value"""
    return _lib.tonglun_get_float64(ctypes.byref(value))


def _get_complex64(value: TLValue) -> complex:
    """Extract 64-bit complex from value"""
    real = ctypes.c_double()
    imag = ctypes.c_double()
    _lib.tonglun_load_complex64(
        ctypes.byref(real), ctypes.byref(imag), ctypes.byref(value)
    )
    free_value(ctypes.byref(value))
    return complex(real.value, imag.value)


def _get_complex32(value: TLValue) -> complex:
    """Extract 32-bit complex from value"""
    real = ctypes.c_float()
    imag = ctypes.c_float()
    _lib.tonglun_load_complex32(
        ctypes.byref(real), ctypes.byref(imag), ctypes.byref(value)
    )
    # no need to free value, because it is stack allocated
    return complex(real.value, imag.value)


def _get_string(value: TLValue) -> str:
    """Extract string from value"""
    c_str = ctypes.c_char_p()
    _lib.tonglun_load_string(ctypes.byref(c_str), ctypes.byref(value))
    result = c_str.value.decode("utf-8") if c_str.value else ""
    free_value(ctypes.byref(value))
    return result

def _get_tuple(value: TLValue) -> tuple:
    """Extract tuple from value"""
    length = _lib.tonglun_get_tuple_length(ctypes.byref(value))
    elements = []
    
    for i in range(length):
        element_value = TLValue()
        result = _lib.tonglun_take_tuple(ctypes.byref(element_value), ctypes.byref(value))
        _check_error(result)
        elements.append(from_value_ref(element_value))
    
    free_value(ctypes.byref(value))
    return tuple(elements)

class ArrayMaker:
    def __init__(self, interface):
        self.__array_interface = interface

def _get_array(value: TLValue) -> np.ndarray:
    """Extract array from handle"""

    data = ctypes.c_void_p()
    dimensions_ptr = ctypes.POINTER(ctypes.c_size_t)()
    strides_ptr = ctypes.POINTER(ctypes.c_int64)()
    ndims = ctypes.c_int64()
    dtype_int = ctypes.c_int()
    readonly = ctypes.c_uint8()
    fortran_order = ctypes.c_uint8()

    handle = value.data

    _lib.tonglun_load_array_info(
        ctypes.byref(data), # data
        ctypes.byref(dimensions_ptr), # dimensions
        ctypes.byref(strides_ptr), # strides
        ctypes.byref(ndims), # ndims
        ctypes.byref(dtype_int), # dtype
        ctypes.byref(readonly), # readonly
        ctypes.byref(fortran_order), # fortran_order
        handle, # handle
    )

    # Convert dimensions and strides to Python lists
    ndims_val = ndims.value
    dimensions = tuple(dimensions_ptr[i] for i in range(ndims_val)) if ndims_val > 0 else ()
    strides = tuple(strides_ptr[i] for i in range(ndims_val)) if ndims_val > 0 else ()
    readonly_val = readonly.value != 0
    fortran_order_val = fortran_order.value != 0

    # Convert dtype to numpy typestr format
    dtype = _DTYPE_TO_NP_DTYPE[TLArrayDType(dtype_int.value)]
    # Create array from interface
    buffer = _PyMemoryView_FromMemory(
        data,
        math.prod(dimensions) * dtype.itemsize,
        0x100 if readonly_val else 0x200,
    )
    array = np.ndarray(
        buffer=buffer,
        shape=dimensions,
        strides=strides,
        order='F' if fortran_order_val else 'C',
        dtype=dtype,
    )
    # Note: Removed weakref.finalize to prevent premature cleanup during function calls
    # The C library will handle cleanup through its own mechanisms
    weakref.finalize(array, free_value, value)
    return array


def from_value_ref(result_value: TLValue):
    # Convert result back to Python
    tag = get_value_tag(result_value)
    if tag == TLValueTag.NONE:
        return None
    elif tag == TLValueTag.BOOL:
        return _get_bool(result_value)
    elif tag == TLValueTag.INT64:
        return _get_int64(result_value)
    elif tag == TLValueTag.FLOAT64:
        return _get_float64(result_value)
    elif tag == TLValueTag.COMPLEX64:
        return _get_complex64(result_value)
    elif tag == TLValueTag.COMPLEX32:
        return _get_complex32(result_value)
    elif tag == TLValueTag.STRING:
        return _get_string(result_value)
    elif tag == TLValueTag.ARRAY:
        return _get_array(result_value)
    elif tag == TLValueTag.HLIST:
        return _get_tuple(result_value)
    else:
        raise ValueError(f"Unsupported return type: {tag}")


def to_value_ref(value) -> TLValue:
    t = type(value)
    factory = _CREATE_VALUE_DISPATCH.get(t, None)
    if factory is None:
        raise ValueError(f"Unsupported Python argument type: {t}")
    return factory(value)


def call_fn(name: str | bytes, *args):
    """Call a registered function by name"""
    # Convert Python arguments to TLValue array
    tl_args = []
    for arg in args:
        tl_args.append(to_value_ref(arg))

    # Create argument array
    args_array = (TLValue * len(tl_args))(*tl_args)
    result_value = TLValue()

    if isinstance(name, str):
        name = name.encode("utf-8")

    # Call function
    result = _lib.tonglun_call_function(
        ctypes.byref(result_value), name, args_array, len(tl_args)
    )
    _check_error(result)

    return from_value_ref(result_value)


def free_value(value: TLValue) -> None:
    """Free a value's resources"""
    _lib.tonglun_free_value(ctypes.byref(value))


_TONGLUN_PY_RUNTIME: None | _TonglunRuntimeType = None


def get_runtime() -> _TonglunRuntimeType:
    global _TONGLUN_PY_RUNTIME
    if _TONGLUN_PY_RUNTIME is None:
        _TONGLUN_PY_RUNTIME = _TonglunRuntimeType()
    return _TONGLUN_PY_RUNTIME


# Export function for easy registration
def export_fn(name: str, func) -> None:
    """Export a Python function to be callable from other languages

    This is the high-level API mentioned in the specification.
    """ 

    get_runtime().register_function(name, func)


export_fn("python.eval", eval)